/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 - 2008 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
/**
 * 
 */
package com.genia.toolbox.projects.csv.business.util.impl;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.genia.toolbox.basics.bean.CsvConfiguration;
import com.genia.toolbox.basics.bean.MutableCsvConfiguration;
import com.genia.toolbox.basics.exception.technical.TechnicalException;
import com.genia.toolbox.basics.exception.technical.TechnicalIOException;
import com.genia.toolbox.basics.manager.CsvManager;
import com.genia.toolbox.basics.manager.ExceptionManager;
import com.genia.toolbox.projects.csv.bean.ConcreteFieldDescriptor;
import com.genia.toolbox.projects.csv.bean.ConstantFieldDescriptor;
import com.genia.toolbox.projects.csv.bean.CsvDataRow;
import com.genia.toolbox.projects.csv.bean.CsvResultList;
import com.genia.toolbox.projects.csv.bean.FieldDescriptor;
import com.genia.toolbox.projects.csv.bean.FolderDescriptor;
import com.genia.toolbox.projects.csv.bean.impl.CsvDataRowImpl;
import com.genia.toolbox.projects.csv.bean.impl.FieldDescriptorVisitorAdapter;
import com.genia.toolbox.projects.csv.business.util.CsvUtils;

/**
 * implementation class for {@link CsvUtils} interface.
 */
public class CsvUtilsImpl
    implements CsvUtils
{

  /**
   * {@link com.genia.toolbox.projects.csv.bean.FieldDescriptorVisitor} that
   * allows to count the expected number of columns of a Csv file.
   */
  private class CountColumnFieldDescriptorVisitor
      extends FieldDescriptorVisitorAdapter
  {
    /**
     * the computed number of columns.
     */
    private int nbColumns = 0;



    /**
     * getter for the nbColumns property.
     * 
     * @return the nbColumns
     */
    public int getNbColumns()
    {
      return nbColumns;
    }



    /**
     * visit method for {@link ConcreteFieldDescriptor}.
     * 
     * @param field
     *          the field to visit
     * @see com.genia.toolbox.projects.csv.bean.impl.FieldDescriptorVisitorAdapter#visitConcreteField(com.genia.toolbox.projects.csv.bean.ConcreteFieldDescriptor)
     */
    @Override
    public void visitConcreteField(ConcreteFieldDescriptor field)
    {
      nbColumns += field.getFieldType().getFieldSize(field);
    }
  }

  /**
   * {@link com.genia.toolbox.projects.csv.bean.FieldDescriptorVisitor} that
   * allows to fill a {@link CsvDataRow}.
   */
  private class FillCsvDataRowFieldDescriptorVisitor
      extends FieldDescriptorVisitorAdapter
  {
    /**
     * the current {@link CsvDataRow} to fill.
     */
    private CsvDataRow currentDataRow;

    /**
     * the current values to fill the {@link CsvDataRow} with.
     */
    private String[] currentValues;

    /**
     * the current index in the currentValues array.
     */
    private int index;



    /**
     * setter for the currentDataRow property.
     * 
     * @param currentDataRow
     *          the currentDataRow to set
     */
    public void setCurrentDataRow(CsvDataRow currentDataRow)
    {
      this.currentDataRow = currentDataRow;
    }



    /**
     * setter for the currentValues property.
     * 
     * @param currentValues
     *          the currentValues to set
     */
    public void setCurrentValues(String[] currentValues)
    {
      this.currentValues = currentValues;
      this.index = 0;
    }



    /**
     * visit method for {@link ConcreteFieldDescriptor}.
     * 
     * @param field
     *          the field to visit
     * @see com.genia.toolbox.projects.csv.bean.impl.FieldDescriptorVisitorAdapter#visitConcreteField(com.genia.toolbox.projects.csv.bean.ConcreteFieldDescriptor)
     */
    @Override
    public void visitConcreteField(ConcreteFieldDescriptor field)
    {
      List<String> fieldValues = new ArrayList<String>();
      for (int i = 0; i < field.getFieldType().getFieldSize(field); i++) {
        fieldValues.add(currentValues[index]);
        index++;
      }
      currentDataRow.getDataRowMap().put(field, fieldValues);
    }



    /**
     * visit method for {@link ConstantFieldDescriptor}.
     * 
     * @param field
     *          the field to visit
     * @see com.genia.toolbox.projects.csv.bean.impl.FieldDescriptorVisitorAdapter#visitConstantField(com.genia.toolbox.projects.csv.bean.ConstantFieldDescriptor)
     */
    @Override
    public void visitConstantField(ConstantFieldDescriptor field)
    {
      currentDataRow.getDataRowMap().put(field, Arrays.asList(field.getValue()));
    }

  }

  /**
   * comment delimiter.
   */
  private static final char COMMENT_DELIMITER = '#';

  /**
   * data delimiter.
   */
  private static final char DELIMITER = ';';

  /**
   * the {@link CsvConfiguration} to use.
   */
  private MutableCsvConfiguration csvConfiguration = null;

  /**
   * the {@link CsvManager} to use.
   */
  private CsvManager csvManager;

  /**
   * the {@link ExceptionManager} to use.
   */
  private ExceptionManager exceptionManager;



  /**
   * getter for the csvManager property.
   * 
   * @return the csvManager
   */
  public CsvManager getCsvManager()
  {
    return csvManager;
  }



  /**
   * getter for the exceptionManager property.
   * 
   * @return the exceptionManager
   */
  public ExceptionManager getExceptionManager()
  {
    return exceptionManager;
  }



  /**
   * read the csv working file for the given folder, and return readed data.
   * 
   * @param folderDescriptor
   *          the description of the folder to parse
   * @param pathToFile
   *          the {@link File} representation of the path to the csv file to
   *          read
   * @return a {@link CsvResultList} object
   * @throws TechnicalException
   *           if an error ocur
   */
  public CsvResultList readCsvData(FolderDescriptor folderDescriptor, File pathToFile)
      throws TechnicalException
  {
    String[][] dataLines = getCsvManager().parseCsv(getCsvConfiguration(), pathToFile);

    CountColumnFieldDescriptorVisitor columnFieldDescriptorVisitor = new CountColumnFieldDescriptorVisitor();
    for (FieldDescriptor field : folderDescriptor.getFields()) {
      field.visit(columnFieldDescriptorVisitor);
    }
    int nbColumns = columnFieldDescriptorVisitor.getNbColumns();

    FillCsvDataRowFieldDescriptorVisitor fillCsvDataRowFieldDescriptorVisitor = new FillCsvDataRowFieldDescriptorVisitor();
    CsvResultList results = new CsvResultList();

    for (int i = 1; i < dataLines.length; i++) {
      String[] currentValues = dataLines[i];
      CsvDataRow csvDataRow = new CsvDataRowImpl(i);
      if (currentValues.length == nbColumns) {
        fillCsvDataRowFieldDescriptorVisitor.setCurrentDataRow(csvDataRow);
        fillCsvDataRowFieldDescriptorVisitor.setCurrentValues(currentValues);
        for (FieldDescriptor field : folderDescriptor.getFields()) {
          field.visit(fillCsvDataRowFieldDescriptorVisitor);
        }
        results.getAcceptedDataRows().add(csvDataRow);
      }
      else {
        results.getIgnoredDataRows().add(csvDataRow);
      }
    }
    return results;

  }



  /**
   * setter for the csvManager property.
   * 
   * @param csvManager
   *          the csvManager to set
   */
  public void setCsvManager(CsvManager csvManager)
  {
    this.csvManager = csvManager;
  }



  /**
   * setter for the exceptionManager property.
   * 
   * @param exceptionManager
   *          the exceptionManager to set
   */
  public void setExceptionManager(ExceptionManager exceptionManager)
  {
    this.exceptionManager = exceptionManager;
  }



  /**
   * for the given folder, write on the working file the csv columns (headers).
   * 
   * @param pathToFile
   *          the {@link File} representation of the path to the csv file to
   *          write
   * @param columns
   *          {@link List} of column names
   * @throws TechnicalException
   *           if an error occured
   */
  public void writeCsvHeaders(File pathToFile, List<String> columns)
      throws TechnicalException
  {
    writeCsvHeaders(pathToFile, columns, null);
  }



  /**
   * for the given folder, write on the working file the csv columns (headers)
   * with some comments after the columns line.
   * 
   * @param pathToFile
   *          the {@link File} representation of the path to the csv file to
   *          write
   * @param columns
   *          {@link List} of column names
   * @param comments
   *          the comments to write with the column names
   * @throws TechnicalException
   *           if an error occured
   */
  public void writeCsvHeaders(File pathToFile, List<String> columns, List<String> comments)
      throws TechnicalException
  {
    try {
      getCsvManager().printCsv(getCsvConfiguration(), comments, Arrays.asList(new String[][] { columns.toArray(new String[columns.size()]) }), new FileOutputStream(pathToFile));
    }
    catch (Exception e) {
      throw getExceptionManager().convertException(e);
    }
  }



  /**
   * write a readme file at the base path.
   * 
   * @param pathToFile
   *          the {@link File} representation of the path to the readme file to
   *          write
   * @param texts
   *          the {@link List} of texts to write
   * @throws TechnicalIOException
   *           if an io error ocur
   */
  public void writeReadmeTexts(File pathToFile, List<String> texts)
      throws TechnicalIOException
  {
    PrintWriter printWriter = null;
    try {
      printWriter = this.getReadmePrintWriter(pathToFile);
      for (String text : texts) {
        printWriter.println(text);
      }
    }
    catch (IOException e) {
      throw getExceptionManager().convertException(e);
    }
    finally {
      if (printWriter != null) {
        printWriter.close();
      }
    }
  }



  /**
   * returns the {@link CsvConfiguration} to use.
   * 
   * @return the {@link CsvConfiguration} to use
   */
  private CsvConfiguration getCsvConfiguration()
  {
    if (csvConfiguration == null) {
      csvConfiguration = getCsvManager().defaultCsvConfiguration();
      csvConfiguration.setCommentDelimiter(COMMENT_DELIMITER);
      csvConfiguration.setDelimiter(DELIMITER);
    }
    return csvConfiguration;
  }



  /**
   * get the {@link PrintWriter} for readme file.
   * 
   * @param pathToFile
   *          the {@link File} representation of the path to the csv file to
   *          read
   * @return the {@link PrintWriter} for readme.
   * @throws IOException
   *           if an io error ocur
   */
  private PrintWriter getReadmePrintWriter(File pathToFile)
      throws IOException
  {
    if (pathToFile.exists()) {
      pathToFile.delete();
    }
    if (!pathToFile.createNewFile()) {
      throw new IOException("unable to create the file " + pathToFile.getAbsolutePath());
    }
    return new PrintWriter(pathToFile);
  }

}
